package com.stox.shared.util;

import java.util.ArrayList;
import java.util.List;

import com.stox.shared.model.AggregateBar;
import com.stox.shared.model.Bar;
import com.stox.shared.model.Kagi;
import com.stox.shared.model.LineBreak;
import com.stox.shared.model.Pnf;
import com.stox.shared.model.Pnf.Box;
import com.stox.shared.model.Range;
import com.stox.shared.model.Renko;
import com.stox.shared.model.enumeration.BarValue;

public abstract class BarUtil {

	public static Double[] values(final List<Bar> bars, final BarValue barValue) {
		final Double[] result = new Double[bars.size()];
		for (int index = 0; index < result.length; index++) {
			result[index] = barValue.get(bars.get(index));
		}
		return result;
	}

	public static Range range(final List<Bar> bars, final BarValue barValue) {
		final Range range = new Range();
		for (final Bar bar : bars) {
			final double value = barValue.get(bar);
			if (range.getHigh() < value) {
				range.setHigh(value);
			}
			if (range.getLow() > value) {
				range.setLow(value);
			}
		}
		return range;
	}

	public static Bar highest(final List<Bar> bars, final BarValue barValue) {
		Bar highest = null;
		for (final Bar bar : bars) {
			if ((null == highest) || (barValue.get(bar) > barValue.get(highest))) {
				highest = bar;
			}
		}
		return highest;
	}

	public static Bar lowest(final List<Bar> bars, final BarValue barValue) {
		Bar lowest = null;
		for (final Bar bar : bars) {
			if ((null == lowest) || (barValue.get(bar) < barValue.get(lowest))) {
				lowest = bar;
			}
		}
		return lowest;
	}

	public static Range highLowRange(final List<Bar> bars) {
		final Range range = new Range();
		range.setHigh(Double.MIN_VALUE);
		range.setLow(Double.MAX_VALUE);
		for (final Bar bar : bars) {
			range.setHigh(Math.max(range.getHigh(), bar.getHigh()));
			range.setLow(Math.min(range.getLow(), bar.getLow()));
		}
		return range;
	}

	public static List<AggregateBar> aggregate(final List<Bar> bars, final double size, final boolean useHighLow) {
		final List<AggregateBar> aggregateBars = new ArrayList<>();
		double pivot = 0;
		double previousPivot = 0;
		AggregateBar aggregateBar = null;
		final double reversalValue = size;
		final List<Bar> waveBars = new ArrayList<>();
		for (int index = bars.size() - 1; index >= 0; index--) {
			final Bar bar = bars.get(index);
			final double value = useHighLow ? (pivot > previousPivot ? bar.getLow() : bar.getHigh()) : bar.getClose();
			waveBars.add(bar);
			if (pivot >= previousPivot) {
				if (value > pivot) {
					pivot = value;
				} else if ((pivot - value) > reversalValue) {
					aggregateBar = new AggregateBar(waveBars, false);
					aggregateBars.add(0, aggregateBar);
					waveBars.clear();
					previousPivot = pivot;
					pivot = value;
				}
			} else {
				if (value < pivot) {
					pivot = value;
				} else {
					aggregateBar = new AggregateBar(waveBars, true);
					aggregateBars.add(0, aggregateBar);
					waveBars.clear();
					previousPivot = pivot;
					pivot = value;
				}
			}
		}
		if ((null != aggregateBar) && !aggregateBars.contains(aggregateBar)) {
			aggregateBars.add(aggregateBar);
		}
		return aggregateBars;
	}

	public static List<Bar> lineBreak(final List<Bar> bars, final int reversalAmount) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final List<Bar> result = new ArrayList<Bar>();
		Bar bar = bars.get(bars.size() - 1);
		LineBreak lineBreak = new LineBreak(true, reversalAmount, bar, null);
		for (int index = bars.size() - 2; index >= 0; index--) {
			bar = bars.get(index);
			final LineBreak aLineBreak = lineBreak.consume(bar);
			if (aLineBreak != lineBreak) {
				result.add(0, lineBreak);
				lineBreak = aLineBreak;
			}
		}
		if (!result.contains(lineBreak)) {
			result.add(0, lineBreak);
		}
		return result;
	}

	public static List<Bar> renko(final List<Bar> bars, final double reversalSize, final boolean reversalSizeInPercentage) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final List<Bar> result = new ArrayList<Bar>();
		final double size = reversalSizeInPercentage ? (reversalSize * bars.get(0).getClose()) / 100 : reversalSize;
		Bar bar = bars.get(bars.size() - 1);
		Renko renko = new Renko(true, size, bar.getClose(), bar.getDate());
		for (int index = bars.size() - 1; index >= 0; index--) {
			bar = bars.get(index);
			final Renko aRenko = renko.consume(bar);
			if (aRenko != renko) {
				result.add(0, renko);
				renko = aRenko;
				index += 1;
			}
		}
		if (!result.contains(renko)) {
			result.add(0, renko);
		}
		return result;
	}

	public static List<Bar> kagi(final List<Bar> bars, final double reversalSize, final boolean reversalSizeInPercentage) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final List<Bar> result = new ArrayList<Bar>();
		final double size = reversalSizeInPercentage ? (reversalSize * bars.get(0).getClose()) / 100 : reversalSize;
		Kagi kagi = new Kagi(true, size, null);
		Bar bar = bars.get(bars.size() - 1);
		kagi.setLow(bar.getClose());

		for (int index = bars.size() - 1; index >= 0; index--) {
			bar = bars.get(index);
			final Kagi aKagi = kagi.consume(bar);
			if (aKagi != kagi) {
				result.add(0, kagi);
				kagi = aKagi;
			}
		}
		if (!result.contains(kagi)) {
			result.add(0, kagi);
		}
		return result;
	}

	public static List<Bar> haikinAshi(final List<Bar> bars) {
		if ((null == bars) || (bars.size() < 2)) {
			return bars;
		}
		final List<Bar> result = new ArrayList<Bar>(bars.size());
		Bar previousAshi = null;
		for (int index = bars.size() - 1; index >= 0; index--) {
			final Bar bar = bars.get(index);
			if (null != previousAshi) {
				final Bar ashi = new Bar();
				ashi.setClose((bar.getOpen() + bar.getHigh() + bar.getLow() + bar.getClose()) / 4);
				ashi.setOpen((previousAshi.getOpen() + previousAshi.getClose()) / 2);
				ashi.setHigh(Math.max(Math.max(bar.getHigh(), ashi.getOpen()), ashi.getClose()));
				ashi.setLow(Math.min(Math.min(bar.getLow(), ashi.getOpen()), ashi.getClose()));
				ashi.setDate(bar.getDate());
				ashi.setScrip(bar.getScrip());
				ashi.setScripId(bar.getScripId());
				ashi.setVolume(bar.getVolume());
				result.add(0, ashi);
			} else {
				result.add(0, bar);
			}
			previousAshi = bar;
		}
		return result;
	}

	public static List<Bar> pnf(final List<Bar> bars, final double boxSize, final int reversalAmount, final boolean boxSizeInPercentage) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final List<Bar> result = new ArrayList<Bar>();
		final double size = boxSizeInPercentage ? (boxSize * bars.get(0).getClose()) / 100 : boxSize;
		final double reversalSize = size * reversalAmount;


		Bar bar = bars.get(bars.size() - 1);
		Box box = new Box(true, size, reversalSize, bar.getClose(), bar);
		Pnf pnf = new Pnf(true, size, reversalSize, bar.getDate());

		for (int index = bars.size() - 1; index >= 0; index--) {
			bar = bars.get(index);
			final Box aBox = box.consume(bar);
			if (aBox != box) {
				pnf.doConsume(box);
				if (aBox.isUp() != pnf.isUp()) {
					result.add(0, pnf);
					pnf = new Pnf(aBox.isUp(), size, reversalSize, bar.getDate());
				}
				box = aBox;
				index++;
			}
		}
		if (!result.contains(pnf)) {
			result.add(0, pnf);
		}
		return result;
	}

	public static void trim(final Bar bar, final double size) {
		final double high = bar.getHigh();
		bar.setHigh(high - (high % size));
		final double low = bar.getLow();
		bar.setLow(low + (size - (low % size)));
	}

}
